import CryptoJS = require("crypto-js");
import { app} from "electron";
import * as fs from "fs";
import NodeRSA = require("node-rsa");
import { $log} from "ts-log-debug";
import uuid = require("uuid");

export interface IKey {
    keyid: string;
    url: string;
    desc: string;
    user: string;
    password: string;
    lastUpdated: string;
}

export class KeyBox {
    public static load( path: string = "data"): KeyBox {
        try {
            const keybox = new KeyBox( path);
            keybox.loadMainKeys();
            return keybox;
        } catch ( e) {
            $log.error( e);
            // KeyBox.log.error( e);
            return null;
        }
    }
    public static create( pwd: string, path: string = "data"): KeyBox {
        const keybox = new KeyBox(path);
        keybox.genAndSaveMainKeys( pwd);
        return keybox;
    }
    public static deleteFolderRecursive( path: string) {
        if ( fs.existsSync( path)) {
            fs.readdirSync( path).forEach( ( file, index) => {
                const curPath = path + "/" + file;
                if ( fs.lstatSync(curPath).isDirectory()) { // recurse
                    KeyBox.deleteFolderRecursive( curPath);
                } else { // delete file
                    fs.unlinkSync(curPath);
                }
            });
            fs.rmdirSync(path);
        }
    }
    public static getWorkPath(): string {
        const env = process.env.NODE_ENV;
        let path: string;
        if ( "dev" !== env && "darwin" === process.platform) {
            path = process.env.HOME + "/Library/Application Support/keybox3/";
        } else {
            path = "./";
        }
        isc.logEcho( path, "path is");
        return path;
    }

    public publicKey: string;
    public privateKey: string;

    private path: string;
    private keyPair: NodeRSA;
    private ds: Isc.DataSource;

    constructor( path: string) {
        this.path = KeyBox.getWorkPath() + path;
        this.keyPair = new NodeRSA({b: 1024});
    }

    public createNewKey() {
        return {
            desc: "",
            keyid: uuid.v4(),
            lastUpdated: new Date().toLocaleDateString(),
            password: "",
            url: "",
            user: "",
        } as IKey;
    }

    public encrypt(str: string): string {
        return this.keyPair.encrypt( str, "base64");
    }
    public decrypt(str: string): string {
        return this.keyPair.decrypt( str, "buffer").toString();
    }
    public changeMainPassword(newpwd: string) {
        const newPath = this.path + "/newMainKeys";
        fs.mkdirSync( newPath);
        fs.writeFileSync( newPath + "/key.public", this.publicKey);

        const encryptedKey = CryptoJS.TripleDES.encrypt( this.privateKey,
            "PrefixPasswd" + newpwd).toString();
        fs.writeFileSync( newPath + "/key.private", encryptedKey);

        const oldPath = this.path + "/mainKeys";
        KeyBox.deleteFolderRecursive( oldPath);
        fs.renameSync( newPath, oldPath);
    }

    public getDataSource() {
        if ( this.ds != null) {
            return this.ds;
        }
        this.ds = isc.DataSource.create({
            ID: "keybox",
            clientOnly: true,
            fields: [
                {
                    hidden: true,
                    name: "keyid",
                    primaryKey: true,
                    type: "sequence",
                },
                {
                    length: 100,
                    name: "url",
                    required: true,
                    title: "地址",
                    type: "text",
                },
                {
                    length: 100,
                    name: "desc",
                    title: "描述",
                    type: "text",
                },
                {
                    length: 100,
                    name: "user",
                    required: true,
                    title: "登录名",
                    type: "text",
                },
                {
                    length: 100,
                    name: "password",
                    required: true,
                    title: "加密密码",
                    type: "text",
                },
                {
                    name: "lastUpdated",
                    title: "更新日期",
                    type: "date",
                },
            ],
        });
        this.ds.setCacheData( this.loadKeys());
        return this.ds;
    }
    public reloadKeys() {
        this.ds.setCacheData( this.loadKeys());
    }
    public saveKey( key: IKey, newKey: boolean, ui: boolean = true) {
        const keyPath = this.path + "/keys/";
        if ( !fs.existsSync( keyPath)) {
            fs.mkdirSync( keyPath);
        }
        fs.writeFileSync( keyPath + key.keyid + ".key", JSON.stringify(key));
        if ( ui) {
            newKey ? this.ds.addData( key) : this.ds.updateData( key);
        }
    }

    public removeKey( key: IKey) {
        const keyPath = this.path + "/keys/";
        fs.unlinkSync( keyPath + key.keyid + ".key");
        this.ds.removeData( key);
    }

    public loadKeys(): IKey[] {
        const keys: IKey[] = [];
        const keyPath = this.path + "/keys/";
        if ( fs.existsSync( keyPath)) {
            fs.readdirSync( keyPath).forEach( ( file, index) => {
                const curPath = keyPath + file;
                if ( !fs.lstatSync( curPath).isDirectory() && file.endsWith( ".key")) {
                    const key = JSON.parse( fs.readFileSync( curPath).toString()) as IKey;
                    keys.push( key);
                }
            });
        }
        return keys;
    }
    public exportKeys() {
        const exportPath = this.path + "/exports/";
        if ( !fs.existsSync( exportPath)) {
            fs.mkdirSync( exportPath);
        }
        let keysTxt = "";
        const keys = this.loadKeys();
        keys.forEach( ( key: IKey, index: number, array: IKey[]) => {
            key.password = this.decrypt( key.password);
            keysTxt += JSON.stringify( key);
            keysTxt += "\r\n";
        });
        fs.writeFileSync( exportPath + "keys.export", keysTxt);
    }

    public checkPassword( mainPwd: string): boolean {
        try {
            const mainPath = this.path + "/mainKeys";
            const privateKeyStr = fs.readFileSync( mainPath + "/key.private").toString();
            this.privateKey = CryptoJS.TripleDES.decrypt( privateKeyStr,
                "PrefixPasswd" + mainPwd).toString( CryptoJS.enc.Utf8);
            this.keyPair.importKey( this.privateKey, "private");
            return true;
        } catch (e) {
            return false;
        }
    }

    private loadMainKeys() {
        const mainPath = this.path + "/mainKeys";
        this.publicKey = fs.readFileSync( mainPath + "/key.public").toString();
        this.keyPair.importKey( this.publicKey, "public");
    }

    private genAndSaveMainKeys( mainPwd: string) {
        this.keyPair.generateKeyPair();
        this.publicKey = this.keyPair.exportKey("public").toString();
        this.privateKey = this.keyPair.exportKey( "private").toString();
        if ( !fs.existsSync( this.path)) {
            fs.mkdirSync( this.path);
        }
        const mainPath = this.path + "/mainKeys";
        if ( !fs.existsSync( mainPath)) {
            fs.mkdirSync( mainPath);
        }
        fs.writeFileSync( mainPath + "/key.public", this.publicKey);

        const encryptedKey = CryptoJS.TripleDES.encrypt( this.privateKey,
            "PrefixPasswd" + mainPwd).toString();

        fs.writeFileSync( mainPath + "/key.private", encryptedKey);
    }
}
